#include "stdafx.h"
#include "Wnd.h"

WNDCLASSEX      CxWnd::s_wcex_;
ATOM            CxWnd::s_atom_          = 0;
LONG            CxWnd::s_count_         = 0;
const TCHAR*    CxWnd::s_classname      = _T("{B0C97297-E7AE-405f-B615-7B434D2DE153}");
const TCHAR*    CxWnd::s_obj_address_   = _T("object_address");

LRESULT CALLBACK CxWnd::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    CxWnd* pObj = (CxWnd*)::GetProp(hWnd, CxWnd::s_obj_address_);
    if (pObj)
    {
        return pObj->ProcessMessage(message, wParam, lParam);
    }
    else
    {
        // Error!!!
    }

    return ::DefWindowProc(hWnd, message, wParam, lParam);
}


CxWnd::CxWnd()
    :m_hWnd(NULL)
{
    if (0 == s_count_)
    {
        s_atom_ = _register_class(::GetModuleHandle(NULL));
    }
    ++s_count_;
}

CxWnd::~CxWnd()
{
    if (--s_count_ == 0)
    {
        _unregister_class();
    }
    if (m_hWnd)
    {
        ::DestroyWindow(m_hWnd);
        m_hWnd = NULL;
    }
}

HWND CxWnd::GetHWND()
{
    return m_hWnd;
}

BOOL CxWnd::Create(int x, int y, int cx, int cy, HWND hParent)
{
    m_hWnd = ::CreateWindow(s_classname
        , _T("xWnd")
        , WS_OVERLAPPED | WS_SYSMENU
        , x
        , y
        , cx
        , cy
        , hParent
        , NULL
        , NULL
        , 0);

    if (::IsWindow(m_hWnd))
    {
        ::SetProp(m_hWnd, s_obj_address_, this);
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

VOID CxWnd::Destroy()
{
    ::DestroyWindow(m_hWnd);
    m_hWnd = NULL;
}

BOOL CxWnd::ShowWindow(int nShow)
{
    return ::ShowWindow(m_hWnd, nShow);
}

BOOL CxWnd::CenterWindow(HWND hWndCenter)
{
    assert(::IsWindow(m_hWnd));

    // determine owner window to center against
    DWORD dwStyle = GetStyle();
    if(hWndCenter == NULL)
    {
        if(dwStyle & WS_CHILD)
            hWndCenter = ::GetParent(m_hWnd);
        else
            hWndCenter = ::GetWindow(m_hWnd, GW_OWNER);
    }

    // get coordinates of the window relative to its parent
    RECT rcDlg;
    ::GetWindowRect(m_hWnd, &rcDlg);
    RECT rcArea;
    RECT rcCenter;
    HWND hWndParent;
    if(!(dwStyle & WS_CHILD))
    {
        // don't center against invisible or minimized windows
        if(hWndCenter != NULL)
        {
            DWORD dwStyleCenter = ::GetWindowLong(hWndCenter, GWL_STYLE);
            if(!(dwStyleCenter & WS_VISIBLE) || (dwStyleCenter & WS_MINIMIZE))
                hWndCenter = NULL;
        }

        HMONITOR hMonitor = NULL;
        if(hWndCenter != NULL)
        {
            hMonitor = ::MonitorFromWindow(hWndCenter, MONITOR_DEFAULTTONEAREST);
        }
        else
        {
            hMonitor = ::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
        }

        if (!hMonitor)
            return FALSE;

        MONITORINFO minfo;
        minfo.cbSize = sizeof(MONITORINFO);
        BOOL bResult = ::GetMonitorInfo(hMonitor, &minfo);
        if (!bResult)
            return FALSE;

        rcArea = minfo.rcWork;

        if(hWndCenter == NULL)
            rcCenter = rcArea;
        else
            ::GetWindowRect(hWndCenter, &rcCenter);
    }
    else
    {
        // center within parent client coordinates
        hWndParent = ::GetParent(m_hWnd);
        assert(::IsWindow(hWndParent));

        ::GetClientRect(hWndParent, &rcArea);
        assert(::IsWindow(hWndCenter));
        ::GetClientRect(hWndCenter, &rcCenter);
        ::MapWindowPoints(hWndCenter, hWndParent, (POINT*)&rcCenter, 2);
    }

    int DlgWidth = rcDlg.right - rcDlg.left;
    int DlgHeight = rcDlg.bottom - rcDlg.top;

    // find dialog's upper left based on rcCenter
    int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
    int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;

    // if the dialog is outside the screen, move it inside
    if(xLeft + DlgWidth > rcArea.right)
        xLeft = rcArea.right - DlgWidth;
    if(xLeft < rcArea.left)
        xLeft = rcArea.left;

    if(yTop + DlgHeight > rcArea.bottom)
        yTop = rcArea.bottom - DlgHeight;
    if(yTop < rcArea.top)
        yTop = rcArea.top;

    // map screen coordinates to child coordinates
    return ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1,
        SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}

DWORD CxWnd::GetStyle() const
{
    assert(::IsWindow(m_hWnd));
    return (DWORD)::GetWindowLong(m_hWnd, GWL_STYLE);
}

LRESULT CxWnd::ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
    FILTERS::iterator it = m_ltFilters.begin();

    BOOL bHandled = FALSE;
    LRESULT lRet = 0;

    while (it != m_ltFilters.end())
    {
        //IMsgFilter* filter = it->get();
        xBase::CScopedRefPtr<IMsgFilter> filter = *it;
        if (filter)
        {
            if (!bHandled)
            {
                lRet = filter->process(m_hWnd, message, wParam, lParam, &bHandled);
            }
            else
            {
                BOOL b = FALSE;
                filter->process(m_hWnd, message, wParam, lParam, &b);
            }
        }
        ++it;
    }

    switch (message)
    {
    case WM_DESTROY:
        {
            ::RemoveProp(m_hWnd, s_obj_address_);
            m_hWnd = NULL;
        }
        break;
    }

    return DefWindowProc(message, wParam, lParam);
}

LRESULT CxWnd::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (::IsWindow(m_hWnd))
    {
        return ::DefWindowProc(m_hWnd, message, wParam, lParam);
    }
    return 0;
}

BOOL CxWnd::RegisterFilter(UINT index, IMsgFilter* filter)
{
    FILTERS::iterator it = std::find(m_ltFilters.begin(), m_ltFilters.end(), filter);
    if (it != m_ltFilters.end())
    {
        return FALSE;
    }
    else
    {
        if (0 == index)
        {
            m_ltFilters.push_front(filter);
        }
        else
        {
            // don't support insert filter currently
            m_ltFilters.push_back(filter);
        }
        return TRUE;
    }
}

BOOL CxWnd::UnregisterFilter(UINT index, IMsgFilter* filter)
{
    FILTERS::iterator it = std::find(m_ltFilters.begin(), m_ltFilters.end(), filter);
    if (it != m_ltFilters.end())
    {
        m_ltFilters.erase(it);
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

ATOM CxWnd::_register_class(HINSTANCE hInstance)
{
    memset(&s_wcex_, 0, sizeof(s_wcex_));

    s_wcex_.cbSize = sizeof(WNDCLASSEX);

    s_wcex_.style          = CS_HREDRAW | CS_VREDRAW;
    s_wcex_.lpfnWndProc    = WndProc;
    s_wcex_.cbClsExtra     = 0;
    s_wcex_.cbWndExtra     = 0;
    s_wcex_.hInstance      = hInstance;
    s_wcex_.hIcon          = NULL;
    s_wcex_.hCursor        = LoadCursor(NULL, IDC_ARROW);
    s_wcex_.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    s_wcex_.lpszMenuName   = NULL;
    s_wcex_.lpszClassName  = s_classname;
    s_wcex_.hIconSm        = NULL;

    return RegisterClassEx(&s_wcex_);
}

bool CxWnd::_unregister_class()
{
    ::UnregisterClass(s_wcex_.lpszClassName, ::GetModuleHandle(NULL));
    return true;
}